package com.example.garbage.controller;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.garbage.pojo.*;
import com.example.garbage.service.CandidateService;
import com.example.garbage.service.OdCostlMatrixService;
import com.example.garbage.service.SiteService;
import com.example.garbage.service.TruckService;
import com.google.ortools.Loader;
import com.google.ortools.constraintsolver.*;
import com.google.protobuf.Duration;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.poi.ss.usermodel.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

/**
 * @Author Shunrai
 * @create 2024/3/15 12:25
 * @Version 1.0
 * @Description
 */
@RestController
@RequestMapping("/site")
@ApiResponse(responseCode = "404", description = "查询失败")
@ApiResponse(responseCode = "200", description = "查询成功")
@Tag(name = "垃圾投放点")
public class SiteController {
    @Autowired
    private SiteService siteService;
    @Autowired
    private OdCostlMatrixService odCostlMatrixService;
    @Autowired
    private TruckService truckService;
    @Autowired
    private CandidateService candidateService;

    Integer[] depotIds;
    Integer[] siteIds;
    Integer[] stationIds;
    private int sitenum;
    private int stationnum;
    private int depotnum;

    CompletableFuture<Void> future;
    CompletableFuture<Void> future1;

    @GetMapping("/getAll")
    @Operation(summary = "分页获取所有垃圾投放点")
    public RespBean<SiteDTO> getAllGSite(@RequestParam(value = "current",required = false)Integer current,@RequestParam(value = "PAGE_SIZE",required = false)Integer PAGE_SIZE,
                                      @RequestParam(value = "keyword",required = false)@Parameter(description = "默认为'',搜索关键词") String keyword){
        Wrapper<Site> address;
        if(current==null||"".equals(current)){
            current=1;
        }
        if (keyword==null||"".equals(keyword) ) {
            keyword = "%";
            address= new QueryWrapper<Site>().like("address", keyword).or(i -> i.isNull("address"));
        }else {
          address = new QueryWrapper<Site>().like("address", keyword);
        }

        if(PAGE_SIZE==null||"".equals(PAGE_SIZE)){
            PAGE_SIZE= Math.toIntExact(siteService.count());
        }
        Page<Site> personalEBikePage = new Page<>(current, PAGE_SIZE);
        Page<Site> page = siteService.page(personalEBikePage,address);

        SiteDTO siteDTO = new SiteDTO(page.getRecords(), (int) page.getTotal());
        return RespBean.success("查询成功",siteDTO);
    }
//    @GetMapping("/getAll")
//    @Operation(summary = "获取所有垃圾投放点")
//    public RespBean<Site> getAllGSitePage(){
//        List<Site> list = siteService.list();
//        return RespBean.success("查询成功",list );
//    }

    @GetMapping("/getMany/{ids}")
    @Operation(summary = "根据id获取垃圾投放点")
    public RespBean<Site> getDepotByIds(@PathVariable("ids")@Parameter(description = ",分割") String ids){
        String[] split = ids.split(",");
        List<Integer> idList = new ArrayList<>();
        for (String str : split) {
            int number = Integer.parseInt(str);
            idList.add(number);
        }
        List<Site> manyByIds = siteService.getManyByIds(idList);
        return RespBean.success("查询成功",manyByIds);
    }


    @PutMapping("/kitcomputeLM")
    @Operation(summary = "厨余垃圾计算长度od")
    public RespBean computeLengthMatrix1(@RequestBody Map<String, Object> param){
        Integer[] depotids = {(Integer) param.get("processids")};
        ArrayList<Integer> siteidsList = (ArrayList<Integer>) param.get("siteids");
        Object[] siteidsObjArray = siteidsList.toArray();
        Integer[] siteids = new Integer[siteidsObjArray.length];
        for (int i = 0; i < siteidsObjArray.length; i++) {
            siteids[i] = Integer.parseInt((String) siteidsObjArray[i]);
        }
        ArrayList<Integer> stationidsList = (ArrayList<Integer>) param.get("stationids");
        Object[] stationidsObjArray = stationidsList.toArray();
        Integer[] stationids = new Integer[stationidsObjArray.length];

        for (int i = 0; i < stationidsObjArray.length; i++) {
            stationids[i] =Integer.parseInt ((String) stationidsObjArray[i]) ;
        }
        future1 = siteService.computeLengthMatrixKit(siteids, stationids, depotids);

        return RespBean.success("更新成功");
    }

    @GetMapping("/kitcheck")
    @Operation(summary = "厨余垃圾计算完成？")
    public RespBean progress1(){
        if (future1!=null&&future1.isDone()) {
            return RespBean.success("成功","完成");
        } else {
            // 异步方法尚未完成
            // 执行其他逻辑
            return RespBean.success("成功","没有");
        }
    }
    @PutMapping("/computeLM")
    @Operation(summary = "计算长度od")
    public RespBean computeLengthMatrix(@RequestBody Map<String, Object> param){
        Integer[] depotids = {(Integer) param.get("depotids")};
        ArrayList<Integer> siteidsList = (ArrayList<Integer>) param.get("siteids");
        Object[] siteidsObjArray = siteidsList.toArray();
        Integer[] siteids = new Integer[siteidsObjArray.length];
        for (int i = 0; i < siteidsObjArray.length; i++) {
            siteids[i] = Integer.parseInt((String) siteidsObjArray[i]);
        }
        ArrayList<Integer> stationidsList = (ArrayList<Integer>) param.get("stationids");
        Object[] stationidsObjArray = stationidsList.toArray();
        Integer[] stationids = new Integer[stationidsObjArray.length];

        for (int i = 0; i < stationidsObjArray.length; i++) {
            stationids[i] =Integer.parseInt ((String) stationidsObjArray[i]) ;
        }
        future = siteService.computeLengthMatrix(siteids, stationids, depotids);

        return RespBean.success("更新成功");
    }

    @GetMapping("/check")
    @Operation(summary = "计算完成？")
    public RespBean progress(){
        if (future.isDone()) {
            return RespBean.success("成功","完成");
        } else {
            // 异步方法尚未完成
            // 执行其他逻辑
            return RespBean.success("成功","没有");
        }
    }

    @PostMapping("/routePlan/{ids}/{type}")
    @Operation(summary = "路径规划")
    public RespBean<RoutePlanDTO[]> routePlan(@PathVariable("ids")@Parameter(description = "车辆主键ids") String ids,@RequestBody()@Parameter(description = "请求体参数")  RoutePlanRequest param,@PathVariable("type")@Parameter(description = "1其他垃圾，2厨余垃圾") String type){
//        Integer[] depotids = {(Integer) param.depotids};
        ArrayList<Integer> depotids1 = (ArrayList<Integer>) param.depotids;
        Object[] depotidsObjArray = depotids1 .toArray();
        Integer[] depotids = new Integer[depotidsObjArray.length];
        for (int i = 0; i <depotidsObjArray.length; i++) {
            depotids[i] = (Integer) depotidsObjArray[i];
        }
        ArrayList<Integer> siteidsList = (ArrayList<Integer>) param.siteids;
        Object[] siteidsObjArray = siteidsList.toArray();
        Integer[] siteids = new Integer[siteidsObjArray.length];
        for (int i = 0; i < siteidsObjArray.length; i++) {
            siteids[i] = Integer.parseInt((String) siteidsObjArray[i]);
        }
        ArrayList<Integer> stationidsList = (ArrayList<Integer>) param.stationids;
        Object[] stationidsObjArray = stationidsList.toArray();
        Integer[] stationids = new Integer[stationidsObjArray.length];
        for (int i = 0; i < stationidsObjArray.length; i++) {
            stationids[i] =Integer.parseInt ((String) stationidsObjArray[i]) ;
        }
        sitenum=siteids.length;
        stationnum=stationids.length;
        depotnum=depotids.length;
        depotIds=depotids;
        siteIds=siteids;
        stationIds=stationids;
        String[] split = ids.split(",");
        long[] vehicleCapacities=new long[split.length] ;
        String[] vehicleName=new String[split.length];

        Integer[] loadC=new
                Integer[split.length];
        int[] starts = new int[stationnum];
        int[] ends= new int[stationnum];
        int j=0;
        int total=sitenum+stationnum+depotnum;
        for (String s : split) {
            Truck truck = truckService.getById(Integer.parseInt(s));
            vehicleCapacities[j]=truck.getLoad()*1000;
            vehicleName[j]=truck.getCarnumber();
            loadC[j]=truck.getLoad();
            j++;
        }

        double[][] distanceMatrix=new double[total][total];
        long[] demands=new long[total];
        int vehicleNumber = split.length;
        int depot = 0;


        Loader.loadNativeLibraries();
        // Instantiate the data problem.
//        final DataModel data = new DataModel();
//        List<OdCostlMatrix> oSite = odCostlMatrixService.list(new QueryWrapper<OdCostlMatrix>().eq("o_site_id", 1));
//        System.out.println(oSite.stream().map(v-> Optional.ofNullable(v.getCost()).orElse(0.0)).collect(Collectors.toList()));
        int i=0;
        for (Integer siteId : siteIds) {
            List<OdCostlMatrix> oSite = odCostlMatrixService.list(new QueryWrapper<OdCostlMatrix>().eq("o_site_id", siteId));
            distanceMatrix[i]=oSite.stream().mapToDouble(v-> Optional.ofNullable(v.getCost()).orElse(0.0)).toArray();
            Site site = siteService.getById(siteId);
            if(Objects.equals(type, "1")){
                demands[i++]=site.getOgweight();
            }else {
                demands[i++]=site.getKwweight();
            }

        }
        int k=0;
        for (Integer stationId : stationIds) {
            List<OdCostlMatrix> oSite = odCostlMatrixService.list(new QueryWrapper<OdCostlMatrix>().eq("o_station_id", stationId));
            distanceMatrix[i] = oSite.stream().mapToDouble(v -> Optional.ofNullable(v.getCost()).orElse(0.0)).toArray();
            demands[i++]=0;
            starts[k]=total-1;
            ends[k]=total-depotnum-stationnum+k;
            k++;
        }
        for (Integer depotId : depotIds) {
            List<OdCostlMatrix> oSite = odCostlMatrixService.list(new QueryWrapper<OdCostlMatrix>().eq("o_depot_id", depotId));
            distanceMatrix[i]=oSite.stream().mapToDouble(v-> Optional.ofNullable(v.getCost()).orElse(0.0)).toArray();
            demands[i++]=0;
        }


        // Create Routing Index Manager
        RoutingIndexManager manager =
                new RoutingIndexManager(distanceMatrix.length, vehicleNumber, starts, ends);

        // Create Routing Model.
        RoutingModel routing = new RoutingModel(manager);

        // Create and register a transit callback.
        final int scale = 1000;
        final int transitCallbackIndex =
                routing.registerTransitCallback((long fromIndex, long toIndex) -> {
                    // Convert from routing variable Index to user NodeIndex.
                    int fromNode = manager.indexToNode(fromIndex);
                    int toNode = manager.indexToNode(toIndex);
                    return (int)distanceMatrix[fromNode][toNode]*scale;
                });

        // Define cost of each arc.
        routing.setArcCostEvaluatorOfAllVehicles(transitCallbackIndex);

        // Add Capacity constraint.
        final int demandCallbackIndex = routing.registerUnaryTransitCallback((long fromIndex) -> {
            // Convert from routing variable Index to user NodeIndex.
            int fromNode = manager.indexToNode(fromIndex);
            return demands[fromNode];
        });
        routing.addDimensionWithVehicleCapacity(demandCallbackIndex, 0, // null capacity slack
                vehicleCapacities, // vehicle maximum capacities
                true, // start cumul to zero
                "Capacity");


        // Setting first solution heuristic.
        RoutingSearchParameters searchParameters =
                main.defaultRoutingSearchParameters()
                        .toBuilder()
                        .setFirstSolutionStrategy(FirstSolutionStrategy.Value.PATH_CHEAPEST_ARC)
                        .setLocalSearchMetaheuristic(LocalSearchMetaheuristic.Value.GUIDED_LOCAL_SEARCH)
                        .setTimeLimit(Duration.newBuilder().setSeconds(1).build())
                        .build();

        // Solve the problem.
        Assignment solution = routing.solveWithParameters(searchParameters);

        // Print solution on console.
        printSolution(demands,vehicleNumber, routing, manager, solution);
        long[][] routes = new long[vehicleNumber][];
        Double[] totalload=new Double[vehicleNumber];
        Double[] totaldistance= new Double[vehicleNumber];
        j=0;
        for (i = 0; i < vehicleNumber; ++i) {
            long index = routing.start(i);
            long routeDistance = 0;
            long routeLoad = 0;
            String route = "";
            ArrayList<Long> arrayList = new ArrayList<Long>();
            while (!routing.isEnd(index)) {

                long nodeIndex = manager.indexToNode(index);
                arrayList.add(nodeIndex);
                routeLoad += demands[(int) nodeIndex];
                long previousIndex = index;
                index = solution.value(routing.nextVar(index));
                routeDistance += routing.getArcCostForVehicle(previousIndex, index, i);
                j++;
            }
            arrayList.add((long) manager.indexToNode(routing.end(i)));
            routes[i] = new long[arrayList.size()];
            for (int m = 0; m < arrayList.size(); m++) {
                routes[i][m] = arrayList.get(m);
            }
            j++;
            totalload[i]= (double) routeLoad;
            totaldistance[i]=routeDistance/1000.0;
        }


        Integer[][] routeids = new Integer[vehicleNumber][];
        for (int l = 0; l < vehicleNumber; l++) {
            ArrayList<Integer> arrayList = new ArrayList<Integer>();
            for (int i1 = 0; i1 < routes[l].length; i1++) {
                if(i1==0){
                   arrayList.add(depotids[0]);
                } else if (i1==routes[l].length-1) {
                    int l1 = (int) (routes[l][i1] - sitenum );
                    arrayList.add(stationids[l1]);
                }else {
                    int l1 = (int) (routes[l][i1] );
                    arrayList.add(siteids[l1]);
                }
            }
            routeids[l] = new Integer[arrayList.size()];
            for (int m = 0; m < arrayList.size(); m++) {
                routeids[l][m] = arrayList.get(m);
            }
//            System.out.println("****************");
        }
        int number[] =new int[vehicleNumber];
        String[] routeName=new String[vehicleNumber];
        Integer[][] edges = new Integer[vehicleNumber][];
        for (int l = 0; l < vehicleNumber; l++) {
            Integer[] edgeids = siteService.routePlan(routeids[l]);
            Integer[] array = Arrays.stream(edgeids).filter((v) -> v != -1).toArray(Integer[]::new);
            edges[l]=array;
            number[l]=routeids[l].length-2;
            String[] strings = siteService.getRouteName(routeids[l], Integer.valueOf(type));
            StringBuilder sb = new StringBuilder();
            for (int i1 = 0; i1 < strings.length; i1++) {
                sb.append(strings[i1]);
                if(i1<strings.length-1){
                    sb.append("->");
                }
            }
            routeName[l]=sb.toString();
        }
        RoutePlanDTO[] routePlanDTOS = new RoutePlanDTO[vehicleNumber];
        for(int n=0;n<vehicleNumber;n++){
            routePlanDTOS[n]=new RoutePlanDTO(vehicleName[n],loadC[n],edges[n], totalload[n], totaldistance[n],routeName[n],number[n]);
        }
//        RoutePlanDTO routePlanDTO = new RoutePlanDTO(vehicleName,loadC,edges, totalload, totaldistance);

//        Integer[] edges = siteService.routePlan(routeids[0]);
//        for (Integer edge : edges) {
//            System.out.println(edge);
//        }

//        for (int l = 0; l < vehicleNumber; l++) {
//            System.out.println("第"+i);
//            for (long route : routes[l]) {
//                System.out.println(route);
//            }
//            System.out.println("=================");
//            for (Integer routeid : routeids[l]) {
//                System.out.println(routeid);
//            }
//        }
//        System.out.println(distanceMatrix[51][49]*scale);
        return RespBean.success("规划成功",routePlanDTOS );
    }


    private static final Logger logger = Logger.getLogger(SiteController.class.getName());
//    static class DataModel {
//
//        public static double[][] distanceMatrix=new double[88][88];
//
//        public  long[] demands=new long[88];
//        public final long[] vehicleCapacities = {3000, 3000, 3000,3000};
//        public final int vehicleNumber = 4;
//        public final int depot = 0;
//        public final int[] starts = {87, 87, 87, 87};
//        public final int[] ends = {86, 85, 84, 83};
//    }
    /// @brief Print the solution.
    static void printSolution(
            long[] demands,Integer vehicleNumber, RoutingModel routing, RoutingIndexManager manager, Assignment solution) {
        // Solution cost.
        logger.info("Objective: " + solution.objectiveValue());
        // Inspect solution.

        // Display routes
        long totalDistance = 0;
        long totalLoad = 0;
        for (int i = 0; i < vehicleNumber; ++i) {
            long index = routing.start(i);
            logger.info("Route for Vehicle " + i + ":");
            long routeDistance = 0;
            long routeLoad = 0;
            String route = "";
            while (!routing.isEnd(index)) {
                long nodeIndex = manager.indexToNode(index);
                routeLoad += demands[(int) nodeIndex];
                route += nodeIndex + " Load(" + routeLoad + ") -> ";
                long previousIndex = index;
                index = solution.value(routing.nextVar(index));
                routeDistance += routing.getArcCostForVehicle(previousIndex, index, i);
            }
            route += manager.indexToNode(routing.end(i));
            logger.info(route);
            logger.info("Distance of the route: " + routeDistance/1000 + "m");
            totalDistance += routeDistance;
            totalLoad += routeLoad;
        }
        logger.info("Total distance of all routes: " + totalDistance/1000 + "m");
        logger.info("Total load of all routes: " + totalLoad);
    }

    @Data
    static class RoutePlanRequest{
        @Schema(description = "垃圾投放点ids")
        private ArrayList siteids;
        private ArrayList stationids;
        private ArrayList depotids;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static class RateResponse{
        private long dsCount;
        private long wsCount;
    }

    @GetMapping("/getRate")
    public RespBean<RateResponse> getRate(){
        QueryWrapper<Site> ds = new QueryWrapper<Site>().eq("label", "定时投放点");
        QueryWrapper<Site> ws = new QueryWrapper<Site>().eq("label", "误时投放点");
        long counted = siteService.count(ds);
        long counted1 = siteService.count(ws);
        return RespBean.success("查询成功" , new RateResponse(counted,counted1));
    }


    @PostMapping("/upload")
    @Operation(summary = "上传excel文件")
    public RespBean<Candidate> handleUpload(@RequestParam("file") MultipartFile excelFile) {
        try {
            InputStream inputStream = excelFile.getInputStream();
            Workbook workbook = WorkbookFactory.create(inputStream);
            Sheet sheet = workbook.getSheetAt(0);
            int longitudeIndex = -1; // 初始设置为-1，表示未找到"经度"列
            int latitudeIndex = -1;  // 初始设置为-1，表示未找到"纬度"列
            int ogcapacityIndex=-1; //其他垃圾容量
            Row headerRow = sheet.getRow(0); // 第一行为列标题
            for (int i = 0; i < headerRow.getLastCellNum(); i++) {
                Cell cell = headerRow.getCell(i);
                String cellValue = cell.toString();
                if (cellValue.equals("经度")) {
                    longitudeIndex = i; // 找到"经度"列的位置
                } else if (cellValue.equals("纬度")) {
                    latitudeIndex = i; // 找到"纬度"列的位置
                } else if (cellValue.equals("其他垃圾容量/L")) {
                    ogcapacityIndex=i;
                }
            }

            List<Double> longitudeData = new ArrayList<>();
            List<Double> latitudeData = new ArrayList<>();
            List<Double> ogcapacityData= new ArrayList<>();

            for (int i = 0; i < sheet.getPhysicalNumberOfRows(); i++) {
                Row row = sheet.getRow(i);
                if (i == 0) {
                    continue; // 跳过第一行（列标题行）
                }

                Cell longitudeCell = row.getCell(longitudeIndex);
                Cell latitudeCell = row.getCell(latitudeIndex);
                Cell ogcapacityCell= row.getCell(ogcapacityIndex);
                Double longitudeValue =Double.parseDouble( longitudeCell.toString());
                Double latitudeValue =   Double.parseDouble(latitudeCell.toString());
                Double ogcapacityValue  =   Double.parseDouble(ogcapacityCell.toString());

                longitudeData.add(longitudeValue);
                latitudeData.add(latitudeValue);
                ogcapacityData.add(ogcapacityValue);
            }
            candidateService.addBatch(longitudeData.stream()
                    .toArray(Double[]::new),latitudeData.stream()
                    .toArray(Double[]::new),ogcapacityData.stream()
                    .toArray(Double[]::new));
            List<Candidate> list = candidateService.list();
            return RespBean.success("上传成功",list);
        } catch (IOException e) {
            e.printStackTrace();
            return RespBean.success("上传失败");
        }
    }
}
